/* Autostop
*
* Copyright (c) Maciej Gajewski, Grzegorz Latarowski
* http://code.google.com/p/autostop-automaty-drogowe
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. NO WARRANTY OF ANY KIND is expressed or implied.
*/

///\file gnetudp.cpp UPD sending/rcieving tools

// libc
#include <math.h>

// Qt
#include <QByteArray>

// local
#include "gnetudp.h"
#include "gexception.h"

///////////////////////////////////////////////////////////////////
// G U d p S e n d e r
///////////////////////////////////////////////////////////////////

// =============================== 	GUdpSender ==========================
GUdpSender::GUdpSender( QObject* parent ) : QObject( parent )
{
	// intialize
	_datagramSize	= 512;	// small, but safe
	_datagramId		= 0;	// FIXME may couse collisions, intiialize with random
}

// =============================== 	~GUdpSender ==========================
GUdpSender::~GUdpSender()
{
	// nothing
}

// =============================== 	sendSync ==========================
void GUdpSender::sendSync( const QByteArray& data, const QHostAddress & address, quint16 port )
{
	// send asynch
	sendAsync( data, address, port );
	
	// wait for completion
	
	// this yelds warning and propably has no effect
	//_socket. waitForBytesWritten( -1 ); // TODO throw exception if timeout, configure timeout
}

// =============================== 	sendAsync ==========================
void GUdpSender::sendAsync( const QByteArray& data, const QHostAddress & address, quint16 port )
{
	// calculate efective number of bytes sent per datagram, get number of datagrmas to send
	int maxBytesPerDatagram	= _datagramSize - sizeof(  _GUdpDatagramHeader );
	int bytesToSend			= data.size();
	int datgramsInBlock		= (int)ceil( data.size() / (double)maxBytesPerDatagram );
	
	// allocate transfer buffer
	QByteArray	buffer;
	buffer.reserve( maxBytesPerDatagram ); // upper bound
	
	// get packet id
	quint16 blockId = ( _datagramId ++ ) & 0xffff;
	quint16 datagramNumber = 0;
	while( bytesToSend > 0 )
	{
		int bytesInDatagram	= bytesToSend > maxBytesPerDatagram ? maxBytesPerDatagram : bytesToSend;
		//int datagramSize	= bytesInDatagram + sizeof(  _GUdpDatagramHeader );
		
		// debug
		//qDebug("Sending datagram %d/%d of packet %d, size: %d"
		//	, (int)datagramNumber, datgramsInBlock, (int)blockId, bytesInDatagram );
		
		// create QByteArray wrapper around packet data to send in this dastagram
		QByteArray datagramData = QByteArray::fromRawData( data.data() + ( data.size() - bytesToSend ), bytesInDatagram );
		// prepare space for datagram's header
		buffer.resize( sizeof(_GUdpDatagramHeader)  );
		
		// pretend the data begins with header, prepare header
		_GUdpDatagramHeader* pHeader = (_GUdpDatagramHeader* )buffer.data();
		pHeader->blockId		= blockId;
		pHeader->datagramNumber	= datagramNumber++;
		pHeader->datagramsInBlock = datgramsInBlock;
		
		// append datgram data
		buffer.append( datagramData );
		
		// send !
		_socket.writeDatagram( buffer, address, port ); // TODO check result
		
		// update loop counters
		bytesToSend -= bytesInDatagram;
	}
}

	
///////////////////////////////////////////////////////////////////
// G U d p R e c e i v e r
///////////////////////////////////////////////////////////////////
	
// =============================== 	GUdpReceiver ==========================
GUdpReceiver::GUdpReceiver( QObject* parent ) : QObject( parent )
{
	// init
	_congestion = false;
	_timeout = 1000;

	connect( &_socket, SIGNAL( readyRead() ), SLOT( datagramArrived() ) );
}

// =============================== 	~GUdpReceiver ==========================
GUdpReceiver::~GUdpReceiver()
{
	// nope
}

// =============================== 	startReceiving ==========================
void GUdpReceiver::startReceiving( quint16 port, const QHostAddress& address )
{
	if ( ! _socket.bind( address, port ) )
	{
		throw GSysError( QString("Error binding udp socket: " ) + _socket.errorString() );
	}
}

// =============================== 	stopReceiving ==========================
void GUdpReceiver::stopReceiving()
{
	_socket.close(); // TODO does it work?
}

// =============================== 	isReceiving ==========================
bool GUdpReceiver::isReceiving() const
{
	return _socket.state() == QAbstractSocket::BoundState;
}

// =============================== 	setPacketTimeout ==========================
void GUdpReceiver::setPacketTimeout( int msec )
{
	// TODO
}

// =============================== 	datagramArrived ==========================
void GUdpReceiver::datagramArrived()
{
	// max number of datagrams processed in single batch
	// prevents application hangs on datagram floods
	int maxDatagrams	= 500;
	
	// clear queue
	_packetQueue.clear();
	
	// process each incoming datagram
	while( _socket.hasPendingDatagrams() && maxDatagrams > 0 )
	{
		processDatagram();
		maxDatagrams --;
	}
	
	// in congestion-control mode - emit signal with received datagrams
	if ( _congestion && ! _packetQueue.empty() )
	{
		emit packetsReceived( _packetQueue );
		_packetQueue.clear(); // clear local queue
	}
}

// ================================ processDatagram ===========================
void GUdpReceiver::processDatagram()
{
	// get and check datgram size
	size_t datagramSize = _socket.pendingDatagramSize();
	if ( datagramSize < sizeof ( _GUdpDatagramHeader ) )
	{
		throw GValueError( "Datagram too small");
	}
	
	// read datagram
	QByteArray datagram;
	datagram.resize( datagramSize );
	if ( _socket.readDatagram( datagram.data(), datagramSize ) < 0 )
	{
		throw GSysError( QString("Error reading datagram: ") + _socket.errorString() );
	}
	
	// obtain header pointer
	_GUdpDatagramHeader* pHeader = (_GUdpDatagramHeader* )datagram.data();
	
	// check integrity
	if ( pHeader->datagramNumber >= pHeader->datagramsInBlock )
	{
		throw GValueError("Datagram number larger than total datagram count");
	}
	
	// get data block
	QByteArray data( datagram.data() + sizeof ( _GUdpDatagramHeader ), datagramSize - sizeof ( _GUdpDatagramHeader ) );
	
	// first datagram of the packet
	if ( ! _packets.contains( pHeader->blockId ) )
	{
		// prepare packdt descriptor
		_IncomingPacket packet;
		packet.datagramsInPacket = pHeader->datagramsInBlock;
		packet.receivedDatagarams = 0;
		packet.data.resize( pHeader->datagramsInBlock );
		
		_packets.insert( pHeader->blockId, packet );
	}
	
	// debug each tenth packet
	/*
	if ( ( pHeader->datagramNumber % 10 ) == 0  )
	{
		qDebug("packet: %d, datagram %d/%d", pHeader->blockId, pHeader->datagramNumber, pHeader->datagramsInBlock );
	}
	*/
	
	// add data to packet
	_IncomingPacket& packet = _packets[ pHeader->blockId ];
	
	if ( packet.data[ pHeader->datagramNumber ].isNull() )
	{
		packet.data[ pHeader->datagramNumber ] = data;
		packet.receivedDatagarams ++;
	}
	else
	{
		// datagram duplicated - interesring case
		qDebug("Datagram duplicated, packet id: %d, datagram: %d", pHeader->blockId, pHeader->datagramNumber );
	}
	
	// "see" the packet
	packet.lastSeen = QDateTime::currentDateTime();
	
	// is packet completed?
	if ( packet.datagramsInPacket == packet.receivedDatagarams )
	{
		// handle packet
		packetCompleted( pHeader->blockId, packet );
		// remove from packet map
		_packets.remove( pHeader->blockId );
	}
}

// =============================== 	checkForTimeouted ==========================
void GUdpReceiver::checkForTimeouted()
{
	// remove timeouted packets here

	// leave if there is no packets collected
	if( _packets.isEmpty() ) { return; }
	
	// get current time
	QDateTime now = QDateTime::currentDateTime();
	
	// travel all packets, seek for timeouted
	QList<int> toRemove; // list of timeouted packets to remove
	
	QMap<quint16, _IncomingPacket >::iterator i;
	for ( i = _packets.begin(); i != _packets.end(); ++i );
	{
		const _IncomingPacket& packet = i.value();
				
		int diffMs =  packet.lastSeen.time().msecsTo( now.time() );
		// catch timeouted packet, and packets that seems to be from
		// future (broken, or when counter turns around)
		if ( diffMs < 0 || diffMs > _timeout ) 
		{
			toRemove.append( i.key() );
		}
	}
	
	// remove packets
	foreach( int id, toRemove )
	{
		_packets.remove( id );
	}
}

// =============================== 	packetCompleted ==========================
/// Handles completed packet. assembless all data into single buffer, emits signal
void GUdpReceiver::packetCompleted( int id,  const _IncomingPacket& packet )
{
	// assembly packet
	QByteArray packetData;
	
	foreach( QByteArray data, packet.data )
	{
		packetData += data;
	}
	
	if ( _congestion )
	{
		// add packet to queue in congestion control mode
		_packetQueue.enqueue( qMakePair( quint16(id), packetData ) );
	}
	else
	{
		// emit signal directly
		emit packetReceived( id, packetData );
	}
}

// =============================== 	receiveDatagrams ==========================
/// Checks for incoming datagram and processes it. Non-blocking.
/// May be used when there is no event loop in current thread.
void GUdpReceiver::receiveDatagrams()
{
	if ( _socket.hasPendingDatagrams() )
	{
		datagramArrived();
	}
	checkForTimeouted();
}


// eof

